package org.epic.perleditor.editors.perl;

/*
 * (c) Copyright IBM Corp. 2000, 2001.
 * All Rights Reserved.
 */

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;

/**
 * Double click strategy aware of Perl identifier syntax rules.
 */
public class PerlDoubleClickSelector implements ITextDoubleClickStrategy
{
    protected ITextViewer fText;
    protected int fPos;
    protected int fStartPos;
    protected int fEndPos;

    protected static char[] fgBrackets = { '{', '}', '(', ')', '[', ']', '"', '"' };

    /*
     * Create a PerlDoubleClickSelector.
     */
    public PerlDoubleClickSelector()
    {
        super();
    }

    /*
     * Method declared on ITextDoubleClickStrategy
     */
    public void doubleClicked(ITextViewer text)
    {
        fPos = text.getSelectedRange().x;
        if (fPos < 0) return;

        fText = text;
        if (!selectBracketBlock()) selectWord();
    }

    /**
     * Match the brackets at the current selection. Return true if successful,
     * false otherwise.
     */
    protected boolean matchBracketsAt()
    {
        char prevChar, nextChar;

        int i;
        int bracketIndex1 = fgBrackets.length;
        int bracketIndex2 = fgBrackets.length;

        fStartPos = -1;
        fEndPos = -1;

        // get the chars preceding and following the start position
        try
        {

            IDocument doc = fText.getDocument();

            prevChar = doc.getChar(fPos - 1);
            nextChar = doc.getChar(fPos);

            // is the char either an open or close bracket?
            for (i = 0; i < fgBrackets.length; i = i + 2)
            {
                if (prevChar == fgBrackets[i])
                {
                    fStartPos = fPos - 1;
                    bracketIndex1 = i;
                }
            }
            for (i = 1; i < fgBrackets.length; i = i + 2)
            {
                if (nextChar == fgBrackets[i])
                {
                    fEndPos = fPos;
                    bracketIndex2 = i;
                }
            }

            if (fStartPos > -1 && bracketIndex1 < bracketIndex2)
            {
                fEndPos = searchForClosingBracket(
                    fStartPos,
                    prevChar,
                    fgBrackets[bracketIndex1 + 1],
                    doc);
                
                if (fEndPos > -1) return true;
                else fStartPos = -1;
            }
            else if (fEndPos > -1)
            {
                fStartPos = searchForOpenBracket(fEndPos,
                    fgBrackets[bracketIndex2 - 1], nextChar, doc);
                if (fStartPos > -1)
                    return true;
                else
                    fEndPos = -1;
            }

        }
        catch (BadLocationException x)
        {
        }

        return false;
    }

    /**
     * Select the word at the current selection. Return true if successful,
     * false otherwise.
     */
    protected boolean matchWord()
    {
        IDocument doc = fText.getDocument();

        try
        {
            int pos = fPos;
            char c;
            boolean varPrefix = false;

            while (pos >= 0)
            {
                c = doc.getChar(pos);
                if (!varPrefix)
                {
                    if (c == '@' || c == '%' || c == '$') varPrefix = true;
                    else if (!Character.isJavaIdentifierPart(c)) break;                    
                }
                else if (c != '@' && c != '%' && c != '$') break;
                --pos;
            }

            fStartPos = pos;

            pos = fPos;
            int length = doc.getLength();

            while (pos < length)
            {
                c = doc.getChar(pos);
                if (!Character.isJavaIdentifierPart(c))
                    break;
                ++pos;
            }

            fEndPos = pos;

            return true;
        }
        catch (BadLocationException x)
        {
        }

        return false;
    }

    /**
     * Returns the position of the closing bracket after startPosition.
     * 
     * @returns the location of the closing bracket.
     * @param startPosition -
     *            the beginning position
     * @param openBracket -
     *            the character that represents the open bracket
     * @param closeBracket -
     *            the character that represents the close bracket
     * @param document -
     *            the document being searched
     */
    protected int searchForClosingBracket(int startPosition, char openBracket,
        char closeBracket, IDocument document)
        throws BadLocationException
    {
        int stack = 1;
        int closePosition = startPosition + 1;
        int length = document.getLength();
        char nextChar;

        while (closePosition < length && stack > 0)
        {
            nextChar = document.getChar(closePosition);
            if (nextChar == openBracket && nextChar != closeBracket)
                stack++;
            else if (nextChar == closeBracket)
                stack--;
            closePosition++;
        }

        if (stack == 0)
            return closePosition - 1;
        else
            return -1;

    }

    /**
     * Returns the position of the open bracket before startPosition.
     * 
     * @returns the location of the starting bracket.
     * @param startPosition -
     *            the beginning position
     * @param openBracket -
     *            the character that represents the open bracket
     * @param closeBracket -
     *            the character that represents the close bracket
     * @param document -
     *            the document being searched
     */
    protected int searchForOpenBracket(int startPosition, char openBracket,
        char closeBracket, IDocument document)
        throws BadLocationException
    {
        int stack = 1;
        int openPos = startPosition - 1;
        char nextChar;

        while (openPos >= 0 && stack > 0)
        {
            nextChar = document.getChar(openPos);
            if (nextChar == closeBracket && nextChar != openBracket)
                stack++;
            else if (nextChar == openBracket)
                stack--;
            openPos--;
        }

        if (stack == 0)
            return openPos + 1;
        else
            return -1;
    }

    /**
     * Select the area between the selected bracket and the closing bracket.
     * Return true if successful.
     */
    protected boolean selectBracketBlock()
    {
        if (matchBracketsAt())
        {
            if (fStartPos == fEndPos)
                fText.setSelectedRange(fStartPos, 0);
            else
                fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);

            return true;
        }
        return false;
    }

    /**
     * Select the word at the current selection.
     */
    protected void selectWord()
    {
        if (matchWord())
        {
            if (fStartPos == fEndPos)
                fText.setSelectedRange(fStartPos, 0);
            else
                fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);
        }
    }
}
